import { hasChanged, isArray, isObject } from "@vue/shared";
import { track, trigger } from "./effect";
import { TrackOpTypes, TriggerOrTypes } from "./operators";
import { reactive } from "./reactive";


const convert = value => isObject(value) ? reactive(value) : value;

class RefImpl {
  public _value;
  public readonly __v_isRef = true;

  constructor(public rawValue, public shallow) { // 使用了 public 就可以不用在构造函数中通过 this.rawValue = rawValue; 方式赋值了。自动赋值绑定到实例上
    this._value = shallow ? rawValue : convert(rawValue);
  }

  // 这里就涉及到了 Object.defineProperty, 通过 babel 转换成 Object.defineProperty 方式
  // https://babeljs.io/repl#?browsers=ie%2010%20&build=&builtIns=false&corejs=3.6&spec=false&loose=false&code_lz=MYGwhgzhAECC0G8BQ1oQKYBdoDcwgFd0AKAO3QHcA1fIgSkRWgF8mBzLXWkh5VV5kA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=env%2Creact&prettier=false&targets=&version=7.15.3&externalPlugins=&assumptions=%7B%7D
  set value(newValue) {
    // 新旧值判断如果一样，就返回
    if (!hasChanged(newValue, this.rawValue)) return;

    this.rawValue = newValue;
    this._value = this.shallow ? newValue : convert(newValue);
    trigger(this, TriggerOrTypes.SET, 'value', newValue);
  }
  
  get value() {
    track(this, TrackOpTypes.GET, 'value');
    return this._value;
  }
}


function createRef(rawValue, shallow = false) {
  return new RefImpl(rawValue, shallow);
}


/**
 * 将普通类型值变成一个响应式对象
 * ref 针对普通类型本质是：Object.defineProperty(), 如果非得传递对象就使用 Proxy
 * @param value 普通类型值
 */
export function ref(value) {

  return createRef(value);
}

export function shallowRef(value) {
  return createRef(value, true);
}

class ObjectRefImpl {
  public readonly __v_isRef = true;
  public _value;

  constructor(public target, public key) {}

  get value() {
    return this.target[this.key];
  }
  set value(newValue) {
    this.target[this.key] = newValue;
  }
}

/**
 * 把一个对象中某个属性转成一个 ref
 * @param target 对象
 * @param key 属性
 */
export function toRef(target, key) {
  return new ObjectRefImpl(target, key);
}

/**
 * 把对象中的所有属性转成 ref （可能是普通对象，也可能是数组）
 * @param object 对象
 */
export function toRefs(object){

  const ret = isArray(object) ? new Array(object.length) : {};

  for (let key in object) {
    ret[key] = toRef(object, key);
  }

  return ret;
}